{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2023-12-07T08:59:07.651438100Z",
     "start_time": "2023-12-07T08:59:03.316141600Z"
    }
   },
   "outputs": [],
   "source": [
    "import os\n",
    "import time\n",
    "from itertools import chain\n",
    "\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import torch\n",
    "from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score\n",
    "from sklearn.preprocessing import MinMaxScaler\n",
    "from torch import nn\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "# 定义使用的设备GPU or CPU\n",
    "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "# 数据集所在路径（相对）\n",
    "root_dir = \"data\"\n",
    "# 数据集文件名称\n",
    "file_name = \"F10.7.csv\"\n",
    "# dataLoader的batch_size\n",
    "batch_size = 8\n",
    "# LSTM进行预测时使用的过去数据的数量\n",
    "# 查阅论文得知\n",
    "seq_len = 27\n",
    "# 保存的模型位置\n",
    "LSTM_PATH = \"lstm.pth\"\n",
    "# tensorBoard对象\n",
    "writer = SummaryWriter(\"logs\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "outputs": [],
   "source": [
    "# 使用pandas获取数据集文件对象\n",
    "data_file = pd.read_csv(os.path.join(root_dir, file_name))\n",
    "# 读取F10.7指数列\n",
    "F107_data = data_file['F10.7'].values.astype(float)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-12-07T08:59:07.682820100Z",
     "start_time": "2023-12-07T08:59:07.655440800Z"
    }
   },
   "id": "479d25e52f0b284"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "for i, data in enumerate(F107_data):\n",
    "    writer.add_scalar(tag=\"F10.7\", scalar_value=data, global_step=i)"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "e91f46a6c92fafa4"
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "outputs": [],
   "source": [
    "# 实现数据归一化\n",
    "def get_norm_dataLoader(datalist):\n",
    "    norm_scaler = MinMaxScaler(feature_range=(-1, 1))\n",
    "    # 使用sklearn中MinMaxScaler将训练集数据归一化至[-1,1]区间，为了获得更好的训练效果（其他的子集不需要）\n",
    "    norm_data = norm_scaler.fit_transform(datalist.reshape(-1, 1))\n",
    "    # 转换成Tensor类型\n",
    "    norm_data = torch.FloatTensor(norm_data).view(-1)\n",
    "    return norm_data\n",
    "\n",
    "\n",
    "# 生成训练序列，格式为（[（前27个数据），（预测的标签）...]）\n",
    "def generate_sequences(input_data, train_window):\n",
    "    out_seq = []\n",
    "    L = len(input_data)\n",
    "    for j in range(L - train_window):  #从第0项到倒数第27项\n",
    "        pre_seq = input_data[j:j + train_window]  # 前27项\n",
    "        train_label = input_data[j + train_window:j + train_window + 1]  # 第28项\n",
    "        out_seq.append((pre_seq, train_label))\n",
    "    return out_seq"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-12-07T08:59:07.714831Z",
     "start_time": "2023-12-07T08:59:07.682820100Z"
    }
   },
   "id": "60d359e4b259763f"
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "outputs": [],
   "source": [
    "# 创建自己的Dataset类\n",
    "class MyDataset(Dataset):\n",
    "    def __init__(self, data):\n",
    "        self.data = data\n",
    "\n",
    "    def __getitem__(self, item):\n",
    "        return self.data[item]\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self.data)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-12-07T08:59:07.725826400Z",
     "start_time": "2023-12-07T08:59:07.702809700Z"
    }
   },
   "id": "d080860f2469deb5"
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "outputs": [],
   "source": [
    "# 结合论文，按照太阳周期进行划分\n",
    "# 1957-1989年的数据作为训练集\n",
    "raw_train_data = F107_data[0:11380]\n",
    "train_data = get_norm_dataLoader(raw_train_data)\n",
    "\n",
    "# 1989-2000年的数据作为验证集\n",
    "raw_valid_data = F107_data[11380:15392]\n",
    "valid_data = get_norm_dataLoader(raw_valid_data)\n",
    "\n",
    "# 2000-2022年的数据作为测试集\n",
    "raw_test_data = F107_data[15392:23412]\n",
    "test_data = get_norm_dataLoader(raw_test_data)\n",
    "\n",
    "# 生成训练序列\n",
    "train_dataseq = generate_sequences(train_data, seq_len)\n",
    "# 生成Dataset对象 \n",
    "train_dataset = MyDataset(train_dataseq)\n",
    "# 利用DataLoader进行模型数据的输入\n",
    "train_dataloader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True, drop_last=True)\n",
    "\n",
    "# 生成验证序列\n",
    "valid_dataseq = generate_sequences(valid_data, seq_len)\n",
    "# 生成Dataset对象 \n",
    "valid_dataset = MyDataset(valid_dataseq)\n",
    "# 利用DataLoader进行模型数据的输入\n",
    "valid_dataloader = DataLoader(dataset=valid_dataset, batch_size=batch_size, shuffle=True, drop_last=True)\n",
    "\n",
    "# 生成测试序列\n",
    "test_dataseq = generate_sequences(test_data, seq_len)\n",
    "# 生成Dataset对象\n",
    "test_dataset = MyDataset(test_dataseq)\n",
    "# 利用DataLoader进行模型数据的输入\n",
    "test_dataloader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False, drop_last=True)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-12-07T08:59:07.951382600Z",
     "start_time": "2023-12-07T08:59:07.713824700Z"
    }
   },
   "id": "f9436805f3060e72"
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "outputs": [],
   "source": [
    "# 定义LSTM模型\n",
    "class LSTM(nn.Module):\n",
    "    def __init__(self, input_size=1, hidden_size=32, num_layers=2, output_size=1):\n",
    "        super().__init__()\n",
    "        self.input_size = input_size  # 必须，代表输入数据的形状，单变量所以为1\n",
    "        self.hidden_size = hidden_size  # 必须，设置的隐藏层节点个数，可以随意设置\n",
    "        self.num_layers = num_layers  # 必须，LSTM堆叠的层数，默认值是1层\n",
    "        self.output_size = output_size  # 最后输出的数据的形状\n",
    "        self.num_directions = 1  # 单向LSTM为1，双向LSTM则为2\n",
    "        self.batch_size = batch_size  # 使用了的DataLoader设置的batch_size\n",
    "\n",
    "        # 定义LSTM层\n",
    "        self.lstm = nn.LSTM(input_size=input_size, hidden_size=hidden_size, num_layers=num_layers,\n",
    "                            batch_first=True, dropout=0.5)\n",
    "        # 定义batch_first=True，可以使input和output的形状中batch_size提前\n",
    "\n",
    "        # 定义全连接层\n",
    "        self.linear = nn.Linear(self.hidden_size, self.output_size)\n",
    "\n",
    "    def forward(self, input_seq):\n",
    "        seq_loader_len = input_seq.shape[1]\n",
    "        # 27，用来预测的前面的数据数量\n",
    "\n",
    "        # 在前向传播的过程中，每次都随机初始化隐藏层参数，同时学习过程中也不进行隐藏层参数的学习\n",
    "        # h_0(num_directions * num_layers, batch_size, hidden_size)\n",
    "        # c_0(num_directions * num_layers, batch_size, hidden_size)\n",
    "        h0 = torch.randn(self.num_directions * self.num_layers, self.batch_size, self.hidden_size).to(device)\n",
    "        c0 = torch.randn(self.num_directions * self.num_layers, self.batch_size, self.hidden_size).to(device)\n",
    "\n",
    "        # 因为使用了DataLoader和设置了batch_first=True，所以要求输入的数据形式为\n",
    "        # input(batch_size, seq_len, input_size)，所以对输入的序列进行形状转换\n",
    "        input_seq = input_seq.view(self.batch_size, seq_loader_len, self.input_size)\n",
    "\n",
    "        # 传入LSTM层，获得输出，准备传入全连接层\n",
    "        # LSTM层的输入格式为 input, (h_0, c_0) ，输出为output, (h_n, c_n)\n",
    "        # 其中 input为 input(seq_len, batch_size, input_size)\n",
    "        # output为 output(seq_len, batch_size, num_directions * hidden_size)\n",
    "        lstm_out, _ = self.lstm(input_seq, (h0, c0))\n",
    "\n",
    "        # 由于全连接层的形状要求，所以需要将output的第二维度变换为hidden_size\n",
    "        lstm_out = lstm_out.contiguous().view(self.batch_size * seq_len, self.hidden_size)\n",
    "\n",
    "        # 传入全连接层获得输出，此时输出pred的形状和上面的输入lstm_out一样\n",
    "        # 都是(self.batch_size * seq_loader_len, self.hidden_size)\n",
    "        out = self.linear(lstm_out)\n",
    "        # 由于上面将output改变了形状，所以现在进行还原\n",
    "        out = out.view(self.batch_size, seq_len, -1)\n",
    "        # 与DataLoader的标签保持一致，我们只需要取pred第二维度中的最后一个数据，即使形状为（batch_size,1）\n",
    "        out = out[:, -1, :]\n",
    "        return out"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-12-07T08:59:07.965517200Z",
     "start_time": "2023-12-07T08:59:07.959371400Z"
    }
   },
   "id": "50f8d6bc1322f2d0"
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "outputs": [],
   "source": [
    "# 定义模型\n",
    "model = LSTM()\n",
    "model = model.to(device)\n",
    "# 定义损失函数\n",
    "loss_fn = nn.MSELoss()\n",
    "loss_fn = loss_fn.to(device)\n",
    "# 定义优化器\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n",
    "# 定义训练轮数\n",
    "epochs = 20\n",
    "# 记录当前时间\n",
    "start_time = time.time()"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-12-07T08:59:10.361106100Z",
     "start_time": "2023-12-07T08:59:07.970516500Z"
    }
   },
   "id": "7ca947b1acb1203"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "train_all_loss = []\n",
    "valid_all_loss = []\n",
    "for epoch in range(epochs):\n",
    "    # 训练步骤开始\n",
    "    model.train()\n",
    "    train_loss = None\n",
    "    train_iter = 0\n",
    "    train_epoch_loss = 0\n",
    "    for train_iter, (train_seq, y_train) in enumerate(train_dataloader):\n",
    "        # 取出DataLoader中的数据\n",
    "        train_seq = train_seq.to(device)\n",
    "        y_train = y_train.to(device)\n",
    "        # 清空原来的梯度\n",
    "        optimizer.zero_grad()\n",
    "        # 输入到模型\n",
    "        y_train_pred = model(train_seq)\n",
    "        # 获得损失函数的值并且反向传播计算梯度\n",
    "        train_loss = loss_fn(y_train_pred, y_train)\n",
    "        train_loss.backward()\n",
    "        # 使用得到的梯度进行优化\n",
    "        optimizer.step()\n",
    "        train_epoch_loss += train_loss.detach().item()  #每一个批次的损失\n",
    "    # 输出每一轮的损失函数的值\n",
    "    train_epoch_loss /= (train_iter + 1)\n",
    "    print(f'Epoch: {epoch + 1:2} Loss: {train_epoch_loss:10.8f}')\n",
    "    train_all_loss.append(train_epoch_loss)\n",
    "\n",
    "    # 测试步骤开始\n",
    "    model.eval()\n",
    "    valid_loss = None\n",
    "    valid_iter = 0\n",
    "    valid_epoch_loss = 0\n",
    "    with torch.no_grad():\n",
    "        for valid_iter, (valid_seq, y_valid) in enumerate(valid_dataloader):\n",
    "            valid_seq = valid_seq.to(device)\n",
    "            y_valid = y_valid.to(device)\n",
    "            y_valid_pred = model(valid_seq)\n",
    "            valid_loss = loss_fn(y_valid_pred, y_valid)\n",
    "            valid_epoch_loss += valid_loss.detach().item()\n",
    "        valid_epoch_loss /= (valid_iter + 1)\n",
    "        valid_all_loss.append(valid_epoch_loss)\n",
    "\n",
    "# 绘制每一轮的训练和验证损失\n",
    "for i in range(len(train_all_loss)):\n",
    "    writer.add_scalars(\"train_loss\", {'train_loss': train_all_loss[i], 'valid_loss': valid_all_loss[i], }, i)\n",
    "\n",
    "# 保存训练之后的模型参数\n",
    "state = {'model': model.state_dict(), 'optimizer': optimizer.state_dict()}\n",
    "torch.save(state, LSTM_PATH)"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "ab5261eb28ad8fdc"
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "outputs": [],
   "source": [
    "def MAE(y_true, y):\n",
    "    return mean_absolute_error(y_true, y)\n",
    "\n",
    "\n",
    "def RMSE(y_true, y):\n",
    "    return np.sqrt(mean_squared_error(y_true, y))\n",
    "\n",
    "\n",
    "def MASE(y_true, y):\n",
    "    return np.mean(np.abs((np.array(y) - np.array(y_true)) / y_true)) * 100\n",
    "\n",
    "\n",
    "def R2(y_true, y):\n",
    "    return r2_score(y_true, y)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-12-07T08:59:10.377344600Z",
     "start_time": "2023-12-07T08:59:10.365106200Z"
    }
   },
   "id": "d94d0b265ef60f66"
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "MAE= 9.74208613012059\n",
      "RMSE= 21.501025556109983\n",
      "MASE= 10.301805590256697\n",
      "R2= 0.7685295915207263\n"
     ]
    }
   ],
   "source": [
    "scaler = MinMaxScaler(feature_range=(-1, 1))\n",
    "scaler.fit(raw_test_data.reshape(-1, 1))\n",
    "\n",
    "model = LSTM().to(device)\n",
    "model.load_state_dict(torch.load(LSTM_PATH)['model'])\n",
    "model.eval()\n",
    "\n",
    "test_pred = []\n",
    "y_all_test = []\n",
    "for seq, y_test in test_dataloader:\n",
    "    seq = seq.to(device)\n",
    "    seq_len = seq.shape[1]\n",
    "    seq = seq.view(model.batch_size, seq_len, 1)\n",
    "    true_y_test = scaler.inverse_transform(np.array(y_test).reshape(-1, 1))\n",
    "    y_all_test.extend(true_y_test)\n",
    "    with torch.no_grad():\n",
    "        y_test_pred = model(seq)\n",
    "        y_test_pred = list(chain.from_iterable(y_test_pred.data.tolist()))\n",
    "        true_predictions = scaler.inverse_transform(np.array(y_test_pred).reshape(-1, 1))\n",
    "        test_pred.extend(true_predictions)\n",
    "\n",
    "print('MAE=', MAE(y_all_test, test_pred))\n",
    "print('RMSE=', RMSE(y_all_test, test_pred))\n",
    "print('MASE=', MASE(y_all_test, test_pred))\n",
    "print('R2=', R2(y_all_test, test_pred))\n",
    "\n",
    "for i in range(100):\n",
    "    writer.add_scalars(\"LSTM-prediction\", {'true': test_pred[i], 'pred': y_all_test[i], }, i)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2023-12-07T09:00:56.707790900Z",
     "start_time": "2023-12-07T09:00:54.888972700Z"
    }
   },
   "id": "fc42fd11ab1967d0"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [],
   "metadata": {
    "collapsed": false
   },
   "id": "80f5448427ec29fb"
  }
 ],
 "metadata": {
  "kernelspec": {
   "name": "pytorch",
   "language": "python",
   "display_name": "pytorch"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
